package Circle::Wallet;

use 5.006;
use strict;
use warnings;
use autodie;

use Exporter 'import';
use Carp;
use Try::Tiny;
use File::Path     qw(make_path);
use Crypt::PK::ECC;
use Circle::Common qw(load_config http_json_post http_json_get);

our @EXPORT = qw(
  create_wallet
  list_wallet
  balance_of_address
  balance_of_wallet
  assets_of_address
  assets_of_wallet
  send_to
  pay
  let_me_try

  public_address_of_uid
  public_balance_of_address
  public_assets_of_address
  public_search_transaction
  public_try_send
  public_confirm_send
  public_create_wallet
  public_list_wallet
  public_export_wallet
  public_sign_by_private_key
  public_sign_by_public_key
);

=head1 NAME

Circle::Wallet - the circle chain SDK in PERL

=head1 VERSION

Version 0.01

=cut

our $VERSION = '0.01';

=head1 SYNOPSIS

The PERL SDK for circle chain.

This is the simple example:

    use Circle::Wallet;
    # first of all, you must login and then do the following operations.
    # how to login please refer to Circle::User module.
    # first time to use, each user has 3 wallets to create.
    try {
      my $response = create_wallet();
      my $address;
      if ($response->{status} == 200) {
        $address = $response->{data};
      }
      $response = balance_of_address($address);
      // $response->{status} == 200, get the balance: $response->{data}
      $response = assets_of_address($address);
      // $response->{status} == 200, get the assets: $response->{data}
      # if you created 3 wallets
      $response = list_wallet();
      // $response->{status} == 200, get the address list: $response->{data}
    } catch {
      carp 'operation failed: $_';
    }
    ...

=head1 EXPORT

=over 4

=item 1. create_wallet

=item 2. list_wallet

=item 3. balance_of_address

=item 4. balance_of_wallet

=item 5. assets_of_address

=item 6. assets_of_wallet

=item 7. send_to

=item 8. pay

=item 9. let_me_try

=item 10. public_address_of_uid

=item 11. public_balance_of_address

=item 12. public_assets_of_address

=item 13. public_search_transaction

=item 14. public_try_send

=item 15. public_confirm_send

=item 16. public_create_wallet

=item 17. public_list_wallet

=item 18. public_export_wallet

=item 19. public_sign_by_private_key

=item 20. public_sign_by_public_key

=back

=head1 SUBROUTINES/METHODS

=head2 create_wallet

create wallet in the cloud.

=cut

sub create_wallet {
    my $url = _build_create_wallet_url();
    return http_json_post( $url, {} );
}

sub _build_create_wallet_url {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $base_uri    = $wallet_path->{createWallet};
    my $host        = get_host();
    return "${host}${base_uri}";
}

=head2 list_wallet

list the wallet address

   my $response = list_wallet();
   if ($response->{status} == 200) {
      my $addresses = $response->{data};
      // process address list here.
   }

=cut

sub list_wallet {
    my $url = _build_list_wallet_url();
    return http_json_get($url);
}

sub _build_list_wallet_url {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $base_uri    = $wallet_path->{listWallet} . '?phone=';
    my $host        = get_host();
    return "${host}${base_uri}";
}

=head2 balance_of_address

get the balance of the address

   my $response = balance_of_address( $address, $type ); # type: 1 ownership, 2 identity
   if ($response->{status} == 200) {
     my $balance = $response->{data};
     // process the balance here.
   }

=cut

sub balance_of_address {
    my ($address) = @_;
    my $url = _build_balance_of_address($address);
    return http_json_get($url);
}

sub _build_balance_of_address {
    my ($address)   = @_;
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{balanceOfAddress};
    return "${host}${base_uri}?address=${address}";
}

sub get_host {
    my $config   = load_config();
    my $http     = $config->{http};
    my $protocol = $http->{protocol};
    my $host     = $http->{host};
    return "$protocol://$host";
}

=head2 balance_of_wallet

get the balance of the wallets

   $response = balance_of_wallet();
   if ($response->{status} == 200) {
     my $balance_infos = $response->{data};
     // process balance_infos data here.
   }

=cut

sub balance_of_wallet {
    my $url = _build_balance_of_wallet();
    return http_json_get($url);
}

sub _build_balance_of_wallet {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{balanceOfWallet};
    return "${host}${base_uri}";
}

=head2 assets_of_address

get the assets of the address

  my $response = assets_of_address( $address, $type);  # type: 1 ownership, 2 identity
  if ($response->{status} == 200) {
    my $asset_infos = $response->{data};
    // process assets info here.
  }

=cut

sub assets_of_address {
    my ( $address, $type ) = @_;
    my $url = _build_assets_of_address( $address, $type );
    return http_json_get($url);
}

sub _build_assets_of_address {
    my ( $address, $type ) = @_;
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{assetsOfAddress};
    return "${host}${base_uri}?address=${address}&type=${type}";
}

=head2 assets_of_wallet

get the assets of the wallet

  my $response = assets_of_wallet();
  if ($response->{status} == 200) {
    my $assets_infos = $response->{data};
    // process assets info here.
  }

=cut

sub assets_of_wallet {
    my $url = _build_assets_of_wallet();
    return http_json_get($url);
}

sub _build_assets_of_wallet {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{assetsOfWallet};
    return "${host}${base_uri}";
}

=head2 send_to

send assets to others

  my $response = send_to({
    from => '<from-address>',
    address => '<to-address>',
    email => 'test@gmail.com',
    transContent => {
      type => 1, # 1 ownership, 2 identity
      valueHex => '<asset uuid hex string>', # uuid hex string or (asset uuid hex string + '' + data hash string)
    },
    payPassword => '<pay password>'
  });
  if ($response->{status} == 200) {
    my $success = $response->{data};
    // check the sendTo api success or failure here.
  }

=cut

sub send_to {
    my ($req) = @_;
    my $url = _build_send_to();
    return http_json_post( $url, $req );
}

sub _build_send_to {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{sendTo};
    return "${host}${base_uri}";
}

=head2 pay

pay balance to others

  my $response = pay({
    from => '<from-address>',
    to => '<to-address>',
    value => 1000, # send 1000 li to others.
    payPassword => '<pay password>'
  });
  if ($response->{status} == 200) {
    my $success = $response->{data};
    // check pay api success or failure.
  }

=cut

sub pay {
    my ($req) = @_;
    my $url = _build_pay();
    return http_json_post( $url, $req );
}

sub _build_pay {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{pay};
    return "${host}${base_uri}";
}

=head2 let_me_try

let me try to mine the block, and get some assets and balances.

  my $response = let_me_try();
  if ($response->{status} == 200) {
    my $transaction_info = $response->{data};
    // process transaction info here.
  }

=cut

sub let_me_try {
    my $url = _build_let_me_try();
    return http_json_post($url);
}

sub _build_let_me_try {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{letMeTry};
    return "${host}${base_uri}";
}

=head2 public_address_of_uid

public api without user login, address of uid.

  my $response = public_address_of_uid( $uid );
  if ($response->{status} == 200) {
    my $address = $response->{data};
    // process address code here.
  }

=cut

sub public_address_of_uid {
    my ($uid) = @_;
    my $url = _build_public_address_uid($uid);
    return http_json_get($url);
}

sub _build_public_address_uid {
    my ($uid)       = @_;
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{getAddressByUid};
    return "${host}${base_uri}?uid=${uid}";
}

=head2 public_balance_of_address

public api without user login. balance_of_address

  my $response = public_balance_of_address( $address );
  if ($response->{status} == 200) {
    my $balance = $response->{data};
    // process balance code here.
  }

=cut

sub public_balance_of_address {
    my ($address) = @_;
    my $url = _build_public_address_address($address);
    return http_json_get($url);
}

sub _build_public_address_address {
    my ($address)   = @_;
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{getBalanceByAddress};
    return "${host}${base_uri}?address=${address}";
}

=head2 public_assets_of_address

public api without user login, assets of address

  my $response = public_assets_of_address( $address, $type ); # type: 1 ownership 2 identity
  if ($response->{status} == 200) {
    my $assets = $response->{data};
    // process assets here.
  }

=cut

sub public_assets_of_address {
    my ( $address, $type ) = @_;
    my $url = _build_public_assets_of_address( $address, $type );
    return http_json_get($url);
}

sub _build_public_assets_of_address {
    my ( $address, $type ) = @_;
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{getAssetsOfAddress};
    return "${host}${base_uri}?address=${address}&type=${type}";
}

=head2 public_search_transaction

public api without user login, search transaction

  my $response = public_search_transaction({
    address => '<address>',
    inOut => 'IN', # IN/OUT
    transactionContent => {
      type => 0, # 0 balance, 1 ownership, 2 identity
       # uid hex string or (uid hex string + '' + data hash hex)
      valueHex => '<uid hex string>', // optional.
    }
  });
  if ($response->{status} == 200) {
    my $trans = $response->{data};
    // process trans data here.
  }

=cut

sub public_search_transaction {
    my ($req) = @_;
    my $url = _build_public_search_transaction();
    return http_json_post( $url, $req );
}

sub _build_public_search_transaction {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{searchTransaction};
    return "${host}${base_uri}";
}

=head2 public_try_send

public api without user login, try send balance or assets.

  my $from_address = '<from address>';
  my $response = public_try_send({
    from => $from_address,
    address => '<receive address>',
    transContent => {
      type => 0, # 0 balance, 1 ownership, 2 identity
      # for assets: uid hex string or (uid hex string + '' + data hash hex string)
      # for balance: long value hex string.
      valueHex => '<uid hex string>',
    }
  });
  if ($response->{status} == 200) {
    my $tx_hex = $response->{data};
    // process tx hex data here.
  }

=cut

sub public_try_send {
    my ($req) = @_;
    my $url = _build_public_try_send();
    return http_json_post( $url, $req );
}

sub _build_public_try_send {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{trySendTo};
    return "${host}${base_uri}";
}

=head2 public_confirm_send

public api without user login, confirm send balance and assets

   my $tx = decode_json($tx_json_string);
   my $inputs = $tx->{inputs};
   my $keyToSignedDataMap = {};
   foreach my $input (@{$inputs}) {
     my $key = $input->{txIdStr} . ':' . $input->{txOutputIndex};
     $keyToSignedDataMap->{$key} = public_sign_by_private_key( $from_address, $key );
   }
   my $response = public_confirm_send({
      publicKey => '<public key hex string>',
      keyToSignedDataMap => $keyToSignedDataMap,
      unsignedTxJson => $tx_json_string,
   });
  if ($response->{status} == 200) {
    my $success = $response->{data};
    // check the confirm send success or failure.
  }

what's the input key? Transaction input is composed by transaction id and its output index.
So the key of transaction input is: transaction_id + ":" + output_index

=cut

sub public_confirm_send {
    my ($req) = @_;
    my $url = _build_public_confirm_send();
    return http_json_post( $url, $req );
}

sub _build_public_confirm_send {
    my $config      = load_config();
    my $wallet_path = $config->{wallet}->{path};
    my $host        = get_host();
    my $base_uri    = $wallet_path->{confirmSendTo};
    return "${host}${base_uri}";
}

=head2 public_create_wallet

public api without user login, create local wallet.

  my $address = public_create_wallet();
  // process address code here.

=cut

sub public_create_wallet {
    my $home       = $ENV{HOME};
    my $wallet_dir = "$home/.ccl/wallets";
    make_path($wallet_dir);
    my @privates = glob("${wallet_dir}/private_*.pem");
    if ( @privates >= 3 ) {
        croak "you created 3 wallets, you cannot create any more!";
    }

    my $pk         = Crypt::PK::ECC->new();
    my $curve_name = "secp256k1";
    $pk->generate_key($curve_name);

    my $private_key_filename =
      "${wallet_dir}/private_key_" . @privates . ".pem";
    my $private_pem = $pk->export_key_pem('private');
    _save_file( $private_pem, $private_key_filename );

    my $public_key_filename = "${wallet_dir}/public_key_" . @privates . ".pem";
    my $public_pem          = $pk->export_key_pem('public');
    _save_file( $public_pem, $public_key_filename );
}

sub _save_file ( $data, $filepath ) {
    open my $fd, ">:encoding(utf8)", $filepath;
    print $fd $data;
    close($fd);
}

=head2 public_list_wallet

public api without user login, list all local wallet address

  my $addresses = public_list_wallet();
  // process the addresses.

=cut

sub public_list_wallet {
    my $home       = $ENV{HOME};
    my $wallet_dir = "$home/.ccl/wallets";
    my @publics    = glob("${wallet_dir}/public_*.pem");
    foreach my $pub_key_filename (@publics) {
        my $pub            = Crypt::PK::ECC->new($pub_key_filename);
        my $pubckey_octets = $pub->export_key_raw('public_compressed');
        # TODO convert to address.
        say $pubckey_octets;
    }
}

=head2 public_export_wallet

public api without user login, export local wallet data in WIF format.

  my $wifs = public_export_wallet();
  // process the wifs here, keep care, not show to anyone else!

=cut

sub public_export_wallet {
    my $home       = $ENV{HOME};
    my $wallet_dir = "$home/.ccl/wallets";
    my @publics    = glob("${wallet_dir}/public_*.pem");
    foreach my $pub_key_filename (@publics) {
        my $pub            = Crypt::PK::ECC->new($pub_key_filename);
        my $pubckey_octets = $pub->export_key_raw('public_compressed');

        # TODO convert to address.
        say $pubckey_octets;
    }
}

=head2 public_sign_by_private_key

public api without user login, sign data by private key relevant to the address.

  my $signature = public_sign_by_private_key( $address, $data );
  // process signature here.

=cut

sub public_sign_by_private_key {
    
}

=head2 public_sign_by_public_key

public api without user login, sign data by public key relevant to the address.

  # the $data is hex string, the $signature is also hex string.
  my $signature = public_sign_by_public_key( $address, $data );
  // process signature here.

=cut

sub public_sign_by_public_key {

}

=head1 AUTHOR

charles li, C<< <lidh04 at gmail.com> >>

=head1 BUGS

Please report any bugs or feature requests to C<bug-circle-chain at rt.cpan.org>, or through
the web interface at L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Circle-Chain>.  I will be notified, and then you'll
automatically be notified of progress on your bug as I make changes.




=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Circle::Wallet


You can also look for information at:

=over 4

=item * RT: CPAN's request tracker (report bugs here)

L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Circle-Chain>

=item * CPAN Ratings

L<https://cpanratings.perl.org/d/Circle-Chain>

=item * Search CPAN

L<https://metacpan.org/release/Circle-Chain>

=back


=head1 ACKNOWLEDGEMENTS

This module belong to author C<lidh04@gmail>

=cut

=head1 LICENSE AND COPYRIGHT

This software is Copyright (c) 2024 by charles li.

This is free software, licensed under:

  The Artistic License 2.0 (GPL Compatible)


=cut

1;    # End of Circle::Wallet
